package com.sugar.psych.assessment.module.business.service.userpointsrecord;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sugar.psych.assessment.framework.common.exception.ServiceException;
import com.sugar.psych.assessment.module.business.controller.app.userpointsrecord.vo.AppUserPointsRecordRespVO;
import com.sugar.psych.assessment.module.business.controller.app.userpointsrecord.vo.AppUserPointsRespVO;
import com.sugar.psych.assessment.module.business.dal.dataobject.user.UserDO;
import com.sugar.psych.assessment.module.business.dal.mysql.user.BizUserMapper;
import com.sugar.psych.assessment.module.business.enums.PointRecordClsEnum;
import com.sugar.psych.assessment.module.business.util.ObjLockUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import com.sugar.psych.assessment.module.business.controller.admin.userpointsrecord.vo.*;
import com.sugar.psych.assessment.module.business.dal.dataobject.userpointsrecord.UserPointsRecordDO;
import com.sugar.psych.assessment.framework.common.pojo.PageResult;
import com.sugar.psych.assessment.framework.common.pojo.PageParam;
import com.sugar.psych.assessment.framework.common.util.object.BeanUtils;

import com.sugar.psych.assessment.module.business.dal.mysql.userpointsrecord.UserPointsRecordMapper;

import static com.sugar.psych.assessment.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.sugar.psych.assessment.framework.common.util.collection.CollectionUtils.convertList;
import static com.sugar.psych.assessment.framework.common.util.collection.CollectionUtils.diffList;
import static com.sugar.psych.assessment.module.business.enums.ErrorCodeConstants.*;
import static com.sugar.psych.assessment.module.business.enums.PointRecordClsEnum.*;

/**
 * 用户积分记录 Service 实现类
 *
 * @author Ysugar
 */
@Slf4j
@Service
@Validated
public class UserPointsRecordServiceImpl implements UserPointsRecordService {

    @Resource
    private UserPointsRecordMapper userPointsRecordMapper;
    @Resource
    private BizUserMapper bizUserMapper;

    @Override
    public void createUserPointsRecord(UserPointsRecordSaveReqVO createReqVO) {
        Long userId = createReqVO.getUserId();
        UserDO user = bizUserMapper.selectById(userId);
        if (user == null)
            throw exception(USER_NOT_EXISTS);

        ReentrantLock lock = ObjLockUtil.obtainLock(ObjLockUtil.generatorKey("POINTS", userId));
        lock.lock();
        try {
            Boolean type = createReqVO.getType();
            Integer obtainedPoints = createReqVO.getObtainedPoints();
            LocalDateTime nowDateTime = LocalDateTime.now();

            Integer totalPoints = user.getTotalPoints();

            if (type) {
                String cls = createReqVO.getCls();
                Long count = 0L;

                // 判断当前积分类型今日是否重复
                PointRecordClsEnum clsEnum = find(cls);
                switch (clsEnum) {
                    case CLS2:// 睡眠时长达标
                    case CLS3:// 日记
                    case CLS4:// 压力自测
                    case CLS6:// 被赞
                        count = userPointsRecordMapper.selectCount(Wrappers.<UserPointsRecordDO>lambdaQuery()
                                .eq(UserPointsRecordDO::getCls, cls)
                                .between(UserPointsRecordDO::getRecordTime, nowDateTime.with(LocalTime.MIN), nowDateTime.with(LocalTime.MAX)));
                        if (count > 0)
                            return;
                        break;
                    case CLS5:// 回复树洞留言 每日最多+12分
                        count = userPointsRecordMapper.selectCount(Wrappers.<UserPointsRecordDO>lambdaQuery()
                                .eq(UserPointsRecordDO::getCls, cls)
                                .between(UserPointsRecordDO::getRecordTime, nowDateTime.with(LocalTime.MIN), nowDateTime.with(LocalTime.MAX)));
                        if (count >= 3)
                            return;
                        break;
                }

                totalPoints += obtainedPoints;
                // 更新用户积分剩余积分
                bizUserMapper.update(UserDO::getId, userId, UserDO::getTotalPoints, totalPoints);
            } else {
                // 查询用户当前剩余积分，是否足够
                if (totalPoints < obtainedPoints)
                    throw exception(USER_POINTS_INSUFFICIENT);

                totalPoints -= obtainedPoints;

                // 更新用户积分剩余积分
                bizUserMapper.update(UserDO::getId, userId, UserDO::getTotalPoints, totalPoints);
            }
            // 插入积分变动记录
            UserPointsRecordDO userPointsRecord = BeanUtils.toBean(createReqVO, UserPointsRecordDO.class);
            userPointsRecord.setRecordTime(nowDateTime);
            userPointsRecord.setTotalPoints(totalPoints);
            userPointsRecordMapper.insert(userPointsRecord);
        } catch (ServiceException e) {
            throw e;
        } catch (Throwable e) {
            log.error("积分变动失败：", e);
            throw exception(USER_POINTS_RECORD_ERROR);
        }finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
    }

    @Override
    public void deleteUserPointsRecord(Long id) {
        // 校验存在
        validateUserPointsRecordExists(id);
        // 删除
        userPointsRecordMapper.deleteById(id);
    }

    @Override
    public void deleteUserPointsRecordListByIds(List<Long> ids) {
        // 校验存在
        validateUserPointsRecordExists(ids);
        // 删除
        userPointsRecordMapper.deleteByIds(ids);
    }

    private void validateUserPointsRecordExists(List<Long> ids) {
        List<UserPointsRecordDO> list = userPointsRecordMapper.selectByIds(ids);
        if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
            throw exception(USER_POINTS_RECORD_NOT_EXISTS);
        }
    }

    private void validateUserPointsRecordExists(Long id) {
        if (userPointsRecordMapper.selectById(id) == null) {
            throw exception(USER_POINTS_RECORD_NOT_EXISTS);
        }
    }

    @Override
    public UserPointsRecordDO getUserPointsRecord(Long id) {
        return userPointsRecordMapper.selectById(id);
    }

    @Override
    public PageResult<UserPointsRecordRespVO> getUserPointsRecordPage(UserPointsRecordPageReqVO pageReqVO) {
        return userPointsRecordMapper.selectPage(pageReqVO);
    }

    @Override
    public AppUserPointsRespVO appGetUserPointsRecord(Long loginUserId) {
        AppUserPointsRespVO respVO = new AppUserPointsRespVO();
        respVO.setTotalPoints(0);
        respVO.setRecords(Collections.emptyList());
        List<AppUserPointsRecordRespVO.PointRecord> pointRecords = userPointsRecordMapper.appSelectList(loginUserId);

        if (CollUtil.isEmpty(pointRecords))
            return respVO;

        Map<String, List<AppUserPointsRecordRespVO.PointRecord>> pointRecordsGroupMap = pointRecords
                .stream().collect(Collectors.groupingBy(AppUserPointsRecordRespVO.PointRecord::getMonthItem));

        respVO.setRecords(pointRecordsGroupMap.keySet().stream().map(k -> {
            AppUserPointsRecordRespVO recordRespVO = new AppUserPointsRecordRespVO();
            recordRespVO.setMonth(k);
            recordRespVO.setPoints(pointRecordsGroupMap.get(k));
            return recordRespVO;
        }).collect(Collectors.toList()));

        respVO.setTotalPoints(pointRecords.stream().mapToInt(AppUserPointsRecordRespVO.PointRecord::getObtainedPoints).sum());

        return respVO;
    }

}